/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2007 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol;

namespace Galaxium.Protocol.Irc
{
	public class IrcFileTransfer : AbstractFileTransfer
	{
		private DccFileConnection _connection;
		private DccConnectionInfo _connectionInfo;
		
		private string _encodedFileName;

		public IrcFileTransfer (IrcSession session, IContact contact, string filename) //outgoing file transfer
			: base (session, contact, true, filename)
		{
			FileInfo fi = new FileInfo (filename);
			string ip = String.Empty;
			
			if (NetworkUtility.UseNAT)
			{
				byte [] array = NetworkUtility.IPAddress.GetAddressBytes();
				Array.Reverse (array);
				ip = BitConverter.ToInt32 (array, 0).ToString ();
			}
			else
			{
				byte [] array = NetworkUtility.LocalIPAddress.GetAddressBytes();
				Array.Reverse (array);
				ip = BitConverter.ToInt32 (array, 0).ToString ();
			}
			
			int port = NetworkUtility.GetUnusedPort ();
			
			this._incoming = false;
			this._connectionInfo = new DccConnectionInfo (false, ip, port);
			this._connection = new DccFileConnection (session, this, _connectionInfo);
			this._encodedFileName = IrcProtocolHelper.EncodeDccFilename (filename);

			TotalBytes = fi.Length;
			
			SendTranferInvitation (ip, port);
			
			ChangeTransferState (TransferState.Pending);
		}

		public IrcFileTransfer (bool incoming, IrcSession session, IContact contact, string ip, int port, string filename, long filesize, string token)
			: base (session, contact, true, filename)
		{
			this._incoming = incoming;
			
			if (port != 0) //normal dcc
				this._connectionInfo = new DccConnectionInfo (incoming, ip, port);
			else { //reverse dcc
				port = NetworkUtility.GetUnusedPort ();
				this._connectionInfo = new DccConnectionInfo (ip, port, token);
			}
			
			this._connection = new DccFileConnection (session, this, _connectionInfo);
			_connection.ErrorOccurred += HandleErrorOccurred;
			this._encodedFileName = IrcProtocolHelper.EncodeDccFilename (filename);
			
			//TODO: not all irc clients send us a correct file size, so the size can be zero
			TotalBytes = filesize;
		}

		void HandleErrorOccurred(object sender, ConnectionErrorEventArgs e)
		{
			ChangeTransferState (TransferState.Failed);
			
			OnTransferFailed (new FileTransferErrorEventArgs (this, e.Description));
		}
		
		public DccConnectionInfo DccConnectionInfo
		{
			get {  return _connectionInfo; }
		}
		
		public string EncodedFileName
		{
			get {  return _encodedFileName; }
		}
		
		private void SendTranferInvitation (string ip, int port)
		{
			StringBuilder dcc = new StringBuilder ();
			dcc.Append (IrcConstants.Markers.Ctcp);
			dcc.Append (IrcConstants.Commands.Dcc);
			dcc.Append (" SEND ");
			
			dcc.Append (_encodedFileName);
			dcc.Append (' ');
			dcc.Append (ip);
			dcc.Append (' ');
			dcc.Append (port);
			dcc.Append (' ');
			dcc.Append (TotalBytes);

			dcc.Append (IrcConstants.Markers.Ctcp);
			
			IrcSession session = Session as IrcSession;
			session.SendPrivateMessage (Contact.DisplayName, dcc.ToString ());
			
			_connection.Connect (); //start listen socket
		}
		
		internal void SendResumeReply ()
		{
			StringBuilder dcc = new StringBuilder ();
			dcc.Append (IrcConstants.Markers.Ctcp);
			dcc.Append (IrcConstants.Commands.Dcc);
			dcc.Append (" ACCEPT ");
			
			dcc.Append (_encodedFileName);
			dcc.Append (' ');
			if (_connectionInfo.IsReverseDcc)
				dcc.Append ('0');
			else
				dcc.Append (_connectionInfo.Port);
			dcc.Append (' ');
			dcc.Append (_transferedBytes);
			if (_connectionInfo.IsReverseDcc) {
				dcc.Append (' ');
				dcc.Append (_connectionInfo.ReverseToken);
			}

			dcc.Append (IrcConstants.Markers.Ctcp);
						
			IrcSession session = Session as IrcSession;
			session.SendPrivateMessage (Contact.DisplayName, dcc.ToString ());
		}
		
		public override void Accept ()
		{
			if (_state == TransferState.Pending)
			{
				ChangeTransferState (TransferState.Started);
				
				if (_incoming)
					_connection.Connect ();
			}
			else
			{
				throw new InvalidOperationException ("You can only accept a file transfer when it is pending.");
			}
		}

		public override void Decline ()
		{
			if (_state == TransferState.Pending)
			{
				ChangeTransferState (TransferState.Declined);
			}
			else
			{
				throw new InvalidOperationException ("You can only decline a file transfer when it is pending.");
			}
		}

		public override void Abort ()
		{
			if (_connection != null)
				_connection.Disconnect ();
			
			ChangeTransferState (TransferState.Aborted);
		}
		
		internal void SetTransferedBytes (long offset)
		{
			_transferedBytes = offset;
		}
		
		internal void ChangeTransferState (TransferState state)
		{
			_state = state;
			
			switch (state)
			{
				case TransferState.Started:
					OnTransferStart (new FileTransferEventArgs (this));
					break;
				case TransferState.Aborted:
					OnTransferLocalAbort (new FileTransferEventArgs (this));
					break;
				case TransferState.Pending:
					//OnTransferStart (new FileTransferEventArgs (this));
					break;
			}
		}
	}
}